M2.855 · Modelos avanzados de minería de datos · PEC1
2023-1 · Máster universitario en Ciencia de datos (Data science)
Estudios de Informática, Multimedia y Telecomunicación
A lo largo de esta práctica veremos cómo aplicar diferentes técnicas para la carga y preparación de datos:
Importante: Cada uno de los ejercicios puede suponer varios minutos de ejecución, por lo que la entrega debe hacerse en formato notebook y en formato html, donde se vea el código, los resultados y comentarios de cada ejercicio. Se puede exportar el notebook a html desde el menú File $\to$ Download as $\to$ HTML.
Importante: Existe un tipo de celda especial para albergar texto. Este tipo de celda os será muy útil para responder a las diferentes preguntas teóricas planteadas a lo largo de cada PEC. Para cambiar el tipo de celda a este tipo, elegid en el menú: Cell $\to$ Cell Type $\to$ Markdown.
Importante: La solución planteada no debe utilizar métodos, funciones o parámetros declarados "deprecated" en futuras versiones.
Para la realización de la práctica necesitaremos las siguientes librerías:
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn import datasets
from sklearn import preprocessing
from sklearn.manifold import TSNE
from sklearn.decomposition import PCA
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from imblearn.over_sampling import RandomOverSampler, SMOTE, ADASYN
from imblearn.under_sampling import RandomUnderSampler, TomekLinks, EditedNearestNeighbours
import matplotlib
import matplotlib.pyplot as plt
pd.set_option('display.max_columns', None)
seed = 100
%matplotlib inline
A lo largo de esta práctica utilizaremos el conjunto de datos Forest Fires Data Set, el cual contiene información relevante acerca de distintos incendios acaecidos en el noreste de Portugal. Cada muestra del conjunto de datos estará formada por el área de bosque quemada y el valor de múltiples factores que podrían ser los detonantes o catalizadores del fuego. El conjunto de datos lo encontraréis en el siguiente enlace: https://archive.ics.uci.edu/ml/machine-learning-databases/forest-fires/forestfires.csv.
Nota: para los ejercicios de esta PEC, utilizaremos como variable objetivo el "area": número de hectáreas afectadas por el incendio. El resto de variables del conjunto de datos conformarán los atributos descriptivos.
# Carga de dataset
df = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/forest-fires/forestfires.csv")
# Division de dataset entre variable objetivo (y), y variables descriptivas (X)
X = df.drop(["area"], axis = 1)
y = df[["area"]]
num_filas, num_atributos = X.shape
print('Número de filas (muestras):', num_filas)
Número de filas (muestras): 517
print('Número de atributos:', num_atributos)
Número de atributos: 12
atributos = X.columns.tolist()
print('Nombres de los atributos:', atributos)
Nombres de los atributos: ['X', 'Y', 'month', 'day', 'FFMC', 'DMC', 'DC', 'ISI', 'temp', 'RH', 'wind', 'rain']
Los atributos que contiene el dataset "Forest Fires Data Set" coinciden con lo especificado en la página web. A continuacion se muestran las variables y su descripción:
df.isnull().sum()
X 0 Y 0 month 0 day 0 FFMC 0 DMC 0 DC 0 ISI 0 temp 0 RH 0 wind 0 rain 0 area 0 dtype: int64
Podemos observar que no existen valores nulos o faltantes en el dataset
Debido a que en la página web aparecen las descripciones de las variables del dataset podempos identificar el tipo de variable que son cada una.
#Dividimos el dataset por cada tipo de variable
# Variables categóricas
df_categoricas = X[['month', 'day']]
# Variables geoespaciales
df_geoespaciales = X[['X', 'Y']]
# Variables numericas (eliminamos tambien la variable objetivo area)
df_numericas = X.drop(['month', 'day', 'X', 'Y'], axis=1)
# Conteo de valores para la variable 'month'
month_counts = df_categoricas['month'].value_counts()
# Conteo de valores para la variable 'day'
day_counts = df_categoricas['day'].value_counts()
# Crear un array de posiciones para las barras
x_month = month_counts.index.values
x_day = day_counts.index.values
# Crear el gráfico de barras para la variable 'month'
plt.figure(figsize=(10, 6))
plt.bar(x_month, month_counts, tick_label=month_counts.index)
plt.xlabel('Mes')
plt.ylabel('Cantidad de registros')
plt.title('Distribución de registros por mes')
plt.tight_layout()
plt.show()
# Crear el gráfico de barras para la variable 'day'
plt.figure(figsize=(10, 6))
plt.bar(x_day, day_counts, tick_label=day_counts.index)
plt.xlabel('Día de la semana')
plt.ylabel('Cantidad de registros')
plt.title('Distribución de registros por día de la semana')
plt.tight_layout()
plt.show()
# Calcular estadísticos descriptivos básicos
estadisticos_descriptivos = df_numericas.describe()
# Mostrar estadísticos descriptivos
print("Estadísticos descriptivos básicos:")
print(estadisticos_descriptivos)
# Crear histograma para cada variable numérica
plt.figure(figsize=(12, 10))
for i, col in enumerate(df_numericas.columns):
plt.subplot(3, 4, i + 1)
plt.hist(df_numericas[col], bins=20, range=(df_numericas[col].min(), df_numericas[col].max()))
plt.xlabel(col)
plt.ylabel('Frecuencia')
plt.title(f'Histograma de {col}')
plt.tight_layout()
plt.show()
Estadísticos descriptivos básicos:
FFMC DMC DC ISI temp RH \
count 517.000000 517.000000 517.000000 517.000000 517.000000 517.000000
mean 90.644681 110.872340 547.940039 9.021663 18.889168 44.288201
std 5.520111 64.046482 248.066192 4.559477 5.806625 16.317469
min 18.700000 1.100000 7.900000 0.000000 2.200000 15.000000
25% 90.200000 68.600000 437.700000 6.500000 15.500000 33.000000
50% 91.600000 108.300000 664.200000 8.400000 19.300000 42.000000
75% 92.900000 142.400000 713.900000 10.800000 22.800000 53.000000
max 96.200000 291.300000 860.600000 56.100000 33.300000 100.000000
wind rain
count 517.000000 517.000000
mean 4.017602 0.021663
std 1.791653 0.295959
min 0.400000 0.000000
25% 2.700000 0.000000
50% 4.000000 0.000000
75% 4.900000 0.000000
max 9.400000 6.400000
# Variables geoespaciales
x = df_geoespaciales['X']
Y = df_geoespaciales['Y']
# Crear un histograma bidimensional
plt.figure(figsize=(8, 6))
plt.hist2d(x, Y, bins=(3, 3), cmap='Blues')
plt.colorbar()
plt.title('Histograma Bidimensional de Variables Geoespaciales (X, Y)')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()
En las graficas de las variables categoricas se puede observar que los incendios surgen en mayor cantidad en los meses de verano agosto y septiembre. Respecto a los dias no se encuentran unas diferencias destacables aunque se ve por una pequeña diferencia que los dias mas frecuentes son los fines de semana
Observando las variables numéricas se pueden observar que diferentes variables como FFMC, ISI y rain presentan valores extremos que podrian influir en la realizacion de modelos de prediccion. Observando los valores de los estadisticos podemos extraer que existen variables donde la mediana y la media difieren considerablemente en sus valores y no seguirian una distribucion normal.
Con respecto a las variables geoespaciales, la hipotesis a priori es que donde mas incendios forestales se produce se encuentra en las coordenadas mas orientadas al sur del lugar.
En este apartado exploraremos la relación de los atributos descriptivos con la variable objetivo.
Nota: trataremos de forma diferente los atributos categóricos de los numéricos
import matplotlib.pyplot as plt
# Suponiendo que la variable objetivo es 'area'
variable_objetivo = 'area'
# Variables categóricas
variables_categoricas = ['month', 'day']
# Crear gráficos de histogramas superpuestos para cada variable categórica
for categoria in variables_categoricas:
# Obtener las categorías únicas en la variable categórica
categorias_unicas = df_categoricas[categoria].unique()
# Crear un histograma para cada categoría
plt.figure(figsize=(10, 6))
for cat_unica in categorias_unicas:
plt.hist(df[df_categoricas[categoria] == cat_unica][variable_objetivo],
alpha=0.5, label=f'{cat_unica}')
plt.xlabel('Valor de la variable objetivo')
plt.ylabel('Frecuencia')
plt.title(f'Histograma de {variable_objetivo} por {categoria}')
plt.legend()
plt.show()
Observando los histogramas por separado, vemos como los meses de septiembre y agosto si pueden influir en la predicción de nuestro modelo.
En cambio, con los dias de la semana podemos ver que aparentemente no existe influencia relevante con la variable objetivo
import numpy as np
# Variable objetivo
variable_objetivo = 'area'
# Variables categóricas
variables_categoricas = ['month', 'day']
# Calcular media y desviación estándar para cada categoría de las variables categóricas
for categoria in variables_categoricas:
# Obtener las categorías únicas en la variable categórica
categorias_unicas = df_categoricas[categoria].unique()
print(f'Valores para la variable objetivo "{variable_objetivo}" agrupados por la categoría "{categoria}":\n')
for cat_unica in categorias_unicas:
valores_objetivo = df[df_categoricas[categoria] == cat_unica][variable_objetivo]
media = np.mean(valores_objetivo)
desviacion_estandar = np.std(valores_objetivo)
print(f'Categoría "{categoria}" = {cat_unica}')
print(f'Media de {variable_objetivo}: {media:.2f}')
print(f'Desviación estándar de {variable_objetivo}: {desviacion_estandar:.2f}\n')
Valores para la variable objetivo "area" agrupados por la categoría "month": Categoría "month" = mar Media de area: 4.36 Desviación estándar de area: 9.06 Categoría "month" = oct Media de area: 6.64 Desviación estándar de area: 13.23 Categoría "month" = aug Media de area: 12.49 Desviación estándar de area: 60.20 Categoría "month" = sep Media de area: 17.94 Desviación estándar de area: 87.39 Categoría "month" = apr Media de area: 8.89 Desviación estándar de area: 18.79 Categoría "month" = jun Media de area: 5.84 Desviación estándar de area: 16.38 Categoría "month" = jul Media de area: 14.37 Desviación estándar de area: 50.05 Categoría "month" = feb Media de area: 6.28 Desviación estándar de area: 12.03 Categoría "month" = jan Media de area: 0.00 Desviación estándar de area: 0.00 Categoría "month" = dec Media de area: 13.33 Desviación estándar de area: 6.23 Categoría "month" = may Media de area: 19.24 Desviación estándar de area: 19.24 Categoría "month" = nov Media de area: 0.00 Desviación estándar de area: 0.00 Valores para la variable objetivo "area" agrupados por la categoría "day": Categoría "day" = fri Media de area: 5.26 Desviación estándar de area: 9.95 Categoría "day" = tue Media de area: 12.62 Desviación estándar de area: 33.30 Categoría "day" = sat Media de area: 25.53 Desviación estándar de area: 121.97 Categoría "day" = sun Media de area: 10.10 Desviación estándar de area: 25.94 Categoría "day" = mon Media de area: 9.55 Desviación estándar de area: 33.48 Categoría "day" = wed Media de area: 10.71 Desviación estándar de area: 30.00 Categoría "day" = thu Media de area: 16.35 Desviación estándar de area: 94.57
corr_var = df.drop(columns=["month", "day"]).corr()
corr_var.style.background_gradient(cmap='coolwarm').format(precision=2)
| X | Y | FFMC | DMC | DC | ISI | temp | RH | wind | rain | area | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| X | 1.00 | 0.54 | -0.02 | -0.05 | -0.09 | 0.01 | -0.05 | 0.09 | 0.02 | 0.07 | 0.06 |
| Y | 0.54 | 1.00 | -0.05 | 0.01 | -0.10 | -0.02 | -0.02 | 0.06 | -0.02 | 0.03 | 0.04 |
| FFMC | -0.02 | -0.05 | 1.00 | 0.38 | 0.33 | 0.53 | 0.43 | -0.30 | -0.03 | 0.06 | 0.04 |
| DMC | -0.05 | 0.01 | 0.38 | 1.00 | 0.68 | 0.31 | 0.47 | 0.07 | -0.11 | 0.07 | 0.07 |
| DC | -0.09 | -0.10 | 0.33 | 0.68 | 1.00 | 0.23 | 0.50 | -0.04 | -0.20 | 0.04 | 0.05 |
| ISI | 0.01 | -0.02 | 0.53 | 0.31 | 0.23 | 1.00 | 0.39 | -0.13 | 0.11 | 0.07 | 0.01 |
| temp | -0.05 | -0.02 | 0.43 | 0.47 | 0.50 | 0.39 | 1.00 | -0.53 | -0.23 | 0.07 | 0.10 |
| RH | 0.09 | 0.06 | -0.30 | 0.07 | -0.04 | -0.13 | -0.53 | 1.00 | 0.07 | 0.10 | -0.08 |
| wind | 0.02 | -0.02 | -0.03 | -0.11 | -0.20 | 0.11 | -0.23 | 0.07 | 1.00 | 0.06 | 0.01 |
| rain | 0.07 | 0.03 | 0.06 | 0.07 | 0.04 | 0.07 | 0.07 | 0.10 | 0.06 | 1.00 | -0.01 |
| area | 0.06 | 0.04 | 0.04 | 0.07 | 0.05 | 0.01 | 0.10 | -0.08 | 0.01 | -0.01 | 1.00 |
sns.pairplot(df.drop(columns=["month", "day"]))
<seaborn.axisgrid.PairGrid at 0x18afc9d75b0>
column_sels = ["temp", "RH", "wind", "rain", "ISI"]
fig, axs = plt.subplots(ncols=5, nrows=1, figsize=(20, 5))
index = 0
axs = axs.flatten()
colors=["blue", "orange", "green", "red", "black"]
for i, k in enumerate(column_sels):
ax = sns.regplot(y=df["area"], x=df[k], ax=axs[i], color=colors[i])
ax.set_ybound(0, 200)
plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=5.0)
Observando los graficos podemos detectar que las correlaciones entre las variables "tempo" y "RH" son especialmente bajas, aunque la deteccion de areas quemada se observan en altas temperaturas y con baja humedad.
De la misma manera, en las variables que menos tienen correlacion tambien se detecta la escasez de correlacion.
Una vez analizados los atributos descriptivos, es el momento de prepararlos para que nos sean útiles de cara a predecir valores. En este apartado:
# Obtener variables binarias para las columnas categóricas
df_categoricas_binarias = pd.get_dummies(df_categoricas)
# Eliminar las columnas originales categóricas
X = X.drop(['month', 'day'], axis=1)
# Concatenar las variables binarias al DataFrame original
X = pd.concat([X, df_categoricas_binarias], axis=1)
# Visualizar el DataFrame resultante
print(X.head())
X Y FFMC DMC DC ISI temp RH wind rain month_apr month_aug \ 0 7 5 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0 0 1 7 4 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0 0 2 7 4 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0 0 3 8 6 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0 0 4 8 6 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0 0 month_dec month_feb month_jan month_jul month_jun month_mar \ 0 0 0 0 0 0 1 1 0 0 0 0 0 0 2 0 0 0 0 0 0 3 0 0 0 0 0 1 4 0 0 0 0 0 1 month_may month_nov month_oct month_sep day_fri day_mon day_sat \ 0 0 0 0 0 1 0 0 1 0 0 1 0 0 0 0 2 0 0 1 0 0 0 1 3 0 0 0 0 1 0 0 4 0 0 0 0 0 0 0 day_sun day_thu day_tue day_wed 0 0 0 0 0 1 0 0 1 0 2 0 0 0 0 3 0 0 0 0 4 1 0 0 0
scaler = preprocessing.StandardScaler()
X_dummies_scaled = scaler.fit_transform(X)
X_dummies_scaled
array([[ 1.00831277, 0.56986043, -0.80595947, ..., -0.36574845,
-0.37587279, -0.34151233],
[ 1.00831277, -0.24400101, -0.00810203, ..., -0.36574845,
2.66047458, -0.34151233],
[ 1.00831277, -0.24400101, -0.00810203, ..., -0.36574845,
-0.37587279, -0.34151233],
...,
[ 1.00831277, -0.24400101, -1.64008316, ..., -0.36574845,
-0.37587279, -0.34151233],
[-1.58736044, -0.24400101, 0.68095666, ..., -0.36574845,
-0.37587279, -0.34151233],
[ 0.57570057, -1.05786246, -2.02087875, ..., -0.36574845,
2.66047458, -0.34151233]])
X_train, X_test, y_train, y_test = train_test_split(X_dummies_scaled,y)
En nuestro caso, es una buena practica debido a que es un paso a realizar en el preprocesamiento de los datos para prepararlos antes de alimentar a los modelos. el hecho de realizarlos en un paso previo a la division del conjunto nos aportaria una buena distribucion de los datos y evitar la fuga de datos.
La estandarizacion nos aporta la transformacion de los datos para que tengan los mismos parametros que una distribucion normal estandar. De esta forma los algoritmos aprenden de manera mas facil los pesos de los paramentros independientemente de su rango y su escala.
Los escenarios donde la estandarizacion sea imprescindible serian aquellos donde las variables descriptivas se encuentran en una escala totalmente diferentes entre unos y otros. Esto afectaria al algoritmo en su sensibilidad a los cambios o introduccion de nuevas muestras
Con el propósito de comprobar visualmente la distribución de la variable objetivo teniendo en cuenta todos los atributos descriptivos a la vez, vamos a reducir la dimensionalidad del problema a solamente dos atributos que serán la proyección de los atributos descriptivos originales.
pca = PCA(n_components=2, random_state=seed)
X_PCA = pca.fit_transform(X_dummies_scaled)
area_values = y["area"].values
fig, ax = plt.subplots(1, 1, figsize=(8,6))
sc = ax.scatter(x=X_PCA[:,0], y=X_PCA[:,1], c=area_values, cmap='viridis', norm=matplotlib.colors.LogNorm())
ax.set_xlabel("PC 1")
ax.set_ylabel("PC 2")
cbr = plt.colorbar(sc)
cbr.set_label("area", rotation=270)
plt.show()
tsne = TSNE(n_components=2, random_state=seed, init='random', learning_rate='auto')
X_TSNE = tsne.fit_transform(X_dummies_scaled)
# Crear el gráfico de dispersión
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
sc = ax.scatter(x=X_TSNE[:, 0], y=X_TSNE[:, 1], c=area_values, cmap='viridis', norm=matplotlib.colors.LogNorm())
ax.set_xlabel("C 1")
ax.set_ylabel("C 2")
cbr = plt.colorbar(sc)
cbr.set_label("area", rotation=270)
# Mostrar el gráfico
plt.show()
La reduccion se ha creado correctamente pero no creo que haya funcionado bien. Ninguna de las dos dimensiones pueden explicar la variacion de la variable area. Si utilizaramos las nuevas variables no conseguiriamos unos buenos resultados
Los diferentes resultados al aplicar los algoritmos PCA y TSNE vienen por las caracteristicas y funcionamiento de cada una. En el algoritmo PCA la disminucuin de la dimensionaliddad se basa en la minimizacion de las distancias, mientras que el TSNE se basa en mantener las distancias entre los valores en altas y baja dimensionalidad.
En los problemas de clasificación, es muy común encontrar conjuntos de datos muy desbalanceados. En la industria existen múltiples ejemplos, como la detección de fraude o la fuga de clientes. Por este motivo, este ejercicio se centra en el análisis de este tipo de conjuntos.
Vamos a utilizar un conjunto de datos simplificado del data set Turbo Engine del NASA Prognostics Center of Excellence Data Set Repository, el cual sólo estará formado por dos características explicativas, y la variable objetivo, para así poder analizar visualmente el problema de manera sencilla. En este data set tenemos mediciones de los sensores de un motor cada cierto tiempo, siendo la variable objetivo el estado del motor, el cual indica si hay avería o no en el instante de la medición.
Vamos a comenzar cargando el conjunto de datos:
engine_df = pd.read_csv('Turbo_engine.csv', sep=';')
target_feat = 'y'
x1_feat = 'x_1'
x2_feat = 'x_2'
engine_df.head()
| x_1 | x_2 | y | |
|---|---|---|---|
| 0 | 1 | 64182 | 0 |
| 1 | 2 | 64215 | 0 |
| 2 | 3 | 64235 | 0 |
| 3 | 4 | 64235 | 0 |
| 4 | 5 | 64237 | 0 |
A continuación, vamos a analizar la distribución de nuestro conjunto de datos. Para ello, utilizaremos la función show_distribution:
def show_distribution(df):
freq = df[target_feat].value_counts()
plt.pie(freq, labels=('No engine failure ('+str(freq[0])+')', 'Engine failure ('+str(freq[1])+')'), autopct='%1.1f%%')
plt.title("Engine failure distribution")
show_distribution(engine_df)
Cómo se puede observar, el conjunto está muy desbalanceado, ya que sólo 0.5% de las muestras se corresponden con una situación de avería en el motor.
Aprovechando que sólo tenemos dos características descriptivas, vamos a mostrar mediante un scatter plot nuestro conjunto de datos. Para ello utilizaremos la función plot_data. Esta función recibe tres parámetros:
def plot_data(data_sets, only_failures=False, cmap='Paired'):
if not isinstance(data_sets, list):
data_sets = [data_sets]
colors = np.array(["skyblue", "red"])
fig, ax = plt.subplots(len(data_sets), 1, figsize=(12, 7 * len(data_sets)))
for i, data in enumerate(data_sets):
data = data if not only_failures \
else data[data[target_feat] == 1]
eff_ax = ax if len(data_sets) == 1 else ax[i]
X = data[[x1_feat, x2_feat]].values
y = data[target_feat].values
if not only_failures:
clf = LinearDiscriminantAnalysis()
clf.fit(X, y)
h = 2
x_min, x_max = X[:,0].min() - 10*h, X[:,0].max() + 10*h
y_min, y_max = X[:,1].min() - 10*h, X[:,1].max() + 10*h
xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
np.arange(y_min, y_max, h))
Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
eff_ax.contourf(xx, yy, Z, cmap=cmap, alpha=0.25)
eff_ax.contour(xx, yy, Z, colors='black', linewidths=0.7)
eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k')
plot_data([engine_df], only_failures=False)
C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k')
En la imagen anterior, se puede observar la distribución de nuestro conjunto de datos. Cómo ya habíamos analizado, hay muy pocas averías de motor (puntos rojos). En la imagen, además, se puede ver la frontera de decisión del clasificador base. Esta frontera nos permite ver las áreas que el modelo considera que son de una clase y las que considera que son de otra. Al poner encima los puntos vemos si los clasifica correctamente en el área que les corresponde. En este caso, la frontera divide el conjunto de datos en dos partes, teniendo sólo dos puntos en una de ellas (avería de motor, parte superior).
Esta clasificacion se debe a que la linea traza una division donde en la parte de arriba se encontrarian las averias en cualquier caso cumpliendose las caracteristicas. Debido a que las averias son en numero muy inferiores a las no averias ha trazado una linea favorecida a las no averias y asi minimizar el error de detectar no averias en vez de averias.
Seria un poco mas eficiente al medir la precision general del modelo, pero en el caso de buscar averias es podible que no identificaria menos o ninguna que en este caso.
Como queremos averiguar las averias reales, en nuestro caso habria que calcular la sensibilidad o la precision y que mediriamos la proporcion de positivos reales y minimizar los falsos positivos
Para abordar el problema de datos desbalanceados, vamos a analizar la técnica de sobremuestreo (oversampling) de la clase minoritaria. En la literatura hay más técnicas para abordar este problema, como el submuestreo (undersampling) de la clase mayoritaria, pero en esta PEC nos vamos a centrar sólo en esta técnica.
# Extraer las características y la columna de destino
X = engine_df[[x1_feat, x2_feat]]
y = engine_df[target_feat]
# Aplicar Random Over-sampling
ros = RandomOverSampler(sampling_strategy='auto', random_state=10)
X_ros, y_ros = ros.fit_resample(X, y)
resampled_df = pd.DataFrame(data=X_ros, columns=[x1_feat, x2_feat])
resampled_df[target_feat] = y_ros
show_distribution(resampled_df)
from imblearn.over_sampling import SMOTE
smote = SMOTE(random_state=10)
X_resampled_smote, y_resampled_smote = smote.fit_resample(X, y)
resampled_smote_df = pd.DataFrame(data=X_resampled_smote, columns=[x1_feat, x2_feat])
resampled_smote_df[target_feat] = y_resampled_smote
show_distribution(resampled_smote_df)
from imblearn.over_sampling import ADASYN
adasyn = ADASYN(random_state=10)
X_resampled_adasyn, y_resampled_adasyn = adasyn.fit_resample(X, y)
resampled_adasyn_df = pd.DataFrame(data=X_resampled_adasyn, columns=[x1_feat, x2_feat])
resampled_adasyn_df[target_feat] = y_resampled_adasyn
show_distribution(resampled_adasyn_df)
plot_data(engine_df, only_failures=True, cmap='Paired')
C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k')
plot_data(resampled_df, only_failures=True, cmap='Paired')
C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k')
Como podemos observar, las imagenes son casi identicas. Para poder ver la diferencia hay que observar el contorno de los puntos de la grafica no original donde se percibe que los puntos son un poco mas gruesos.
Esto se debe a que, al realizar el incremento de averias con el metodo de duplicación aleatoria, éste se ha fijado en las caracteristicas que tienen las averias del conjunto de datos original y las ha copiado hasta llegar al punto de 50 % averias y 50% no averias
plot_data(engine_df, only_failures=False, cmap='Paired')
plot_data(resampled_df, only_failures=False, cmap='Paired')
C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k') C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k')
Como las averias ya igualan a las no averias la linea se ha desplazado hacia abajo (ampliado la zona de deteccion) para mejorar el modelo para detectar los verdaderos positivos del conjunto de datos
plot_data(resampled_df, only_failures=True, cmap='Paired')
plot_data(resampled_smote_df, only_failures=True, cmap='Paired')
plot_data(resampled_adasyn_df, only_failures=True, cmap='Paired')
C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k') C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k') C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k')
Las diferencias de las tres graficas se deben a como las diferentes tecnicas que se han utilizada para desbalancear el conjunto funcionan:
En la primera imagen con la duplicacion de los valores lo que ha hecho ha sido copiar las caracteristicas de las averias y duplicarlas hasta llegar al 50 % cambiando los valores solo en pocos grados segun los originales.
En la segunda imagen. Las averias originales han sido conectadas con las averias ficticias creadas a partir del algoritmo SMOTE. Esto se debe a que crea las averias nuevas (datos nuevos) en funcion de la linealidad que se presentan entre las averias del conjunto real.
La tercera imagen es practicamente parecida a la segunda cambiando algunos puntos en ellas. Esto se debe a que el algorimo ADASYN funciona parecido a SMOTE pero tiene en cuentra la densidad de los puntos a predecir de los datos originales. Esto quiere decir que crea mas averias en las zonas donde se congregan mas averias en el conjunto original y menos donde menos.
plot_data(resampled_df, only_failures=False, cmap='Paired')
plot_data(resampled_smote_df, only_failures=False, cmap='Paired')
plot_data(resampled_adasyn_df, only_failures=False, cmap='Paired')
C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k') C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k') C:\Users\jlara\AppData\Local\Temp\ipykernel_10440\4157333226.py:34: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored eff_ax.scatter(X[:,0], X[:,1], c=colors[y], cmap=cmap, edgecolors='k')
Las fronteras de decision han cambiado en las 3 imagenes con respecto a la original debido a que han sido introducidos nuevos puntos ficticios (averias). De esta manera el algoritmo ha desplazado la frontera de decision para obtener el mayor de verdaderos positivos en los 3 casos.
Añadiendo comentario sobre las imagenes, se puede observar que las fronteras de SMOTE y ADASYN son ligeramente diferentes a la de duplicidades ya que en ADASYN ha creado mas puntos donde existen mas averias en el conjunto original y menos donde menos. Por este motivo, la frontera de dicesion de este grafico se encuentra un poco mas recogida hacia arriba.
La tecnica SMOTE se deberia de realizar despues de dividir el conjunto original para que la evaluacion del modelo sea mas realista. Esto se debe a que debemos entrenar al modelo con los datos originales y despues poner en practica con datos nuevos que no haya reconocido o no se encuentren en el conjunto original.
Del mismo modo, es posible que exista una fuga de datos debido a que algunos de estos nuevos puntos creados por SMOTE se basen en puntos que ya existan en el conjunto de validacion o test.